package org.adorsys.aderp.aderplogin.client.basic;

import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Collections;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.ModelAttribute;

public class ClientBasicAuthenticator  implements InitializingBean {

	private String credentialsCharset = "UTF-8";
    
    private ClientDetailsService clientDetailsService;

	/**
	 * Use this to inject a principal into the token endpoint.
	 * 
	 * @param clientId
	 * @param redirectUri
	 * @param state
	 * @param scopes
	 * @return
	 */
	@ModelAttribute
	public UsernamePasswordAuthenticationToken authenticate(
			HttpServletRequest request) {

        String header = request.getHeader("Authorization");

        if (header == null || !header.startsWith("Basic ")) {
            return null;
        }
        String[] tokens = extractAndDecodeHeader(header, request);

        assert tokens.length == 2;

        String clientId = tokens[0];
        String password = tokens[1];
        
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
        if(clientDetails==null)
			throw new UsernameNotFoundException(
					"Missing client with id "+ clientId);
        
        if(!StringUtils.equals(password, clientDetails.getClientSecret()))
			throw new BadCredentialsException("Wrong client secret");
        	
        Collection<? extends GrantedAuthority> authorities = Collections.emptyList() ;
		UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(clientId, clientDetails, authorities);
        return result;
	}

	
    /**
     * Decodes the header into a username and password.
     *
     * @throws BadCredentialsException if the Basic header is not present or is not valid Base64
     */
    private String[] extractAndDecodeHeader(String header, HttpServletRequest request) {

        byte[] base64Token;
		try {
			base64Token = header.substring(6).getBytes("UTF-8");
		} catch (UnsupportedEncodingException e1) {
            throw new BadCredentialsException("Failed to decode basic authentication token", e1);
		}
        byte[] decoded;
        try {
            decoded = Base64.decode(base64Token);
        } catch (IllegalArgumentException e) {
            throw new BadCredentialsException("Failed to decode basic authentication token");
        }

        String token;
		try {
			token = new String(decoded, getCredentialsCharset(request));
		} catch (UnsupportedEncodingException e) {
            throw new BadCredentialsException("Failed to decode basic authentication token", e);
		}

        int delim = token.indexOf(":");

        if (delim == -1) {
            throw new BadCredentialsException("Invalid basic authentication token");
        }
        return new String[] {token.substring(0, delim), token.substring(delim + 1)};
    }
	

    public void setCredentialsCharset(String credentialsCharset) {
        Assert.hasText(credentialsCharset, "credentialsCharset cannot be null or empty");
        this.credentialsCharset = credentialsCharset;
    }

    protected String getCredentialsCharset(HttpServletRequest httpRequest) {
        return credentialsCharset;
    }

	public void setClientDetailsService(ClientDetailsService clientDetailsService) {
		this.clientDetailsService = clientDetailsService;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		if (clientDetailsService==null) throw new IllegalStateException("ClientDetailService must be set.");
	}
    
}
